package ginx

import (
	"net/http"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
)

var (
	PageNotFoundRestError     = pageNotFoundRestError{}
	MethodNotAllowedRestError = methodNotAllowedRestError{}
	Http404                   = PageNotFoundRestError
	Http405                   = MethodNotAllowedRestError

	pageNotFoundBody interface{} = gin.H{"detail": "404 not found"}
	methodNotAllowed interface{} = gin.H{"detail": "method not allowed"}

	NotFound   = pageNotFoundBody
	NotAllowed = methodNotAllowed
)

// Set PageNotFoundBody with this Method
func SetPageNotFoundBody(body interface{}) {
	pageNotFoundBody = body
}

// Set MethodNotAllowed with this Method
func SetMethodNotAllowed(body interface{}) {
	methodNotAllowed = body
}

// RestError required HandlerError impl
// HandlerError will be called while panic RestError
type RestError interface {
	HandlerError(context *gin.Context)
}

// impl RestError
type pageNotFoundRestError struct{}

func (pageNotFoundRestError) HandlerError(context *gin.Context) {
	context.JSON(http.StatusNotFound, NotFound)
}

// impl RestError
type methodNotAllowedRestError struct{}

func (methodNotAllowedRestError) HandlerError(context *gin.Context) {
	context.JSON(http.StatusMethodNotAllowed, NotAllowed)
}

// RestError Decorator for gin.HandlerFunc
// Recover RestError when it panic
func RestErrorDecorator(handleFunc gin.HandlerFunc) gin.HandlerFunc {
	return func(context *gin.Context) {
		defer RestErrorMiddleware(context)
		if !context.IsAborted() {
			handleFunc(context)
		} else {
			context.Next()
		}
	}
}

// RestError Decorators for gin.HandlersChain
// Recover RestError when it panic
func ManyRestErrorDecorator(chain gin.HandlersChain) gin.HandlersChain {
	newChain := make(gin.HandlersChain, 0)
	for _, handlerFunc := range chain {
		newChain = append(newChain, RestErrorDecorator(handlerFunc))
	}
	return newChain
}

// Recover RestError when it panic
// Using defer with it
func RestErrorMiddleware(context *gin.Context) {
	if err := recover(); err != nil {
		if restError, ok := err.(RestError); ok {
			restError.HandlerError(context)
			context.Abort()
		} else {
			panic(err)
		}
	}
}

// Default Recovery middleware
func Recovery(context *gin.Context) {
	defer RestErrorMiddleware(context)
	context.Next()
}

// Default returns an gin.Engine instance with the gin.Logger and gin.Recovery and Recovery middleware already attached.
func Default() *gin.Engine {
	server := gin.Default()
	server.Use(Recovery)
	return server
}

// AsMethodHandlerFunc returns an gin.HandlerFunc
// It could make a struct become a gin.HandlerFunc
// panic Http405 if does not match handlerFunc
func AsMethodHandlerFunc(view interface{}) gin.HandlerFunc {

	value := reflect.ValueOf(view)

	r := make(map[string]gin.HandlerFunc)

	for _, requestMethod := range RequestMethods {
		if method := value.MethodByName(requestMethod); method.IsValid() {
			if handlerFunc, ok := method.Interface().(func(ctx *gin.Context)); ok {
				r[requestMethod] = handlerFunc
			}
		}
	}
	return func(context *gin.Context) {
		requestMethod := strings.Title(strings.ToLower(context.Request.Method))
		if handlerFunc := r[requestMethod]; handlerFunc != nil {
			handlerFunc(context)
			context.Next()
		} else {
			panic(Http405)
		}
	}
}
